package cn.anecansaitin.industrialization_expansion.common.item.crafting;

import com.google.common.collect.Lists;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntComparators;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.item.crafting.RecipeItemHelper;
import net.minecraft.tags.ITag;
import net.minecraftforge.common.crafting.IIngredientSerializer;
import net.minecraftforge.fml.DatagenModLoader;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

/**
 * 懒加载的成分，不会在创建之初就读取tag的内容，主要用于tag初始化之前创建
 */
public class LazyIngredient extends Ingredient {
    public static final LazyIngredient EMPTY = new LazyIngredient(null, null);
    private List<ITag.INamedTag<Item>> tags;
    private ItemStack[] items;
    private ItemStack[] itemsCache;
    private IntList stackingIds;
    private boolean isSimple;

    protected LazyIngredient(List<ITag.INamedTag<Item>> tags, ItemStack[] items) {
        super(Stream.of());
        this.items = items;
        this.tags = tags;
    }

    public static LazyIngredient of(@Nullable List<ITag.INamedTag<Item>> tags, @Nullable ItemStack[] items) {
        if (tags == null && items == null)
            return EMPTY;

        return new LazyIngredient(tags, items);
    }

    @Override
    public boolean test(@Nullable ItemStack stack) {
        if (stack == null)
            return false;

        cacheCheck();

        if (itemsCache.length < 1)
            return stack.isEmpty();

        return Arrays.stream(itemsCache).anyMatch(itemStack -> itemStack.getItem() == stack.getItem());
    }

    @Override
    public boolean isEmpty() {
        cacheCheck();
        return itemsCache.length < 1;
    }

    @Override
    protected void invalidate() {
        itemsCache = null;
        stackingIds = null;
        tags = null;
        items = null;
    }

    @Override
    public boolean isSimple() {
        cacheCheck();
        return isSimple;
    }

    @Override
    public @Nonnull JsonElement toJson() {
        JsonObject json = new JsonObject();

        if (items.length > 0) {
            JsonArray array = new JsonArray();
            json.add("item", array);
            Arrays.stream(items).forEach(item -> array.add(Objects.requireNonNull(item.getItem().getRegistryName()).toString()));
        }

        if (!tags.isEmpty()) {
            JsonArray array = new JsonArray();
            json.add("tag", array);
            tags.forEach(tag -> array.add(tag.getName().toString()));
        }

        return json;
    }

    @Override
    public @Nonnull IIngredientSerializer<? extends Ingredient> getSerializer() {
        return LazyIngredientSerializer.INSISTENCE;
    }

    @Override
    public @Nonnull IntList getStackingIds() {
        if (stackingIds == null) {
            cacheCheck();
            stackingIds = new IntArrayList(itemsCache.length);
            Arrays.stream(itemsCache).forEach(item -> stackingIds.add(RecipeItemHelper.getStackingIndex(item)));
            stackingIds.sort(IntComparators.NATURAL_COMPARATOR);
        }

        return stackingIds;
    }

    @Override
    public @Nonnull ItemStack[] getItems() {
        cacheCheck();
        return itemsCache;
    }

    private void cacheCheck() {
        if (itemsCache == null)
            reloadCache();
    }

    private void reloadCache() {
        ArrayList<ItemStack> list = Lists.newArrayList();

        if (tags != null)
            tags.forEach(tag -> tag.getValues().forEach(item -> list.add(item.getDefaultInstance())));

        itemsCache = Stream.concat(list.stream(), Arrays.stream(items == null ? new ItemStack[0] : items)).toArray(ItemStack[]::new);
        isSimple = !DatagenModLoader.isRunningDataGen() && Arrays.stream(itemsCache).noneMatch(item -> item.getItem().isDamageable(item));
    }

    public List<ITag.INamedTag<Item>> getTags() {
        return tags;
    }

    public ItemStack[] getMarkingItems() {
        return items;
    }
}
